/*
 * FakeWidget.java
 *
 * Created on November 9, 2006, 9:55 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package edu.umn.cs5115.scheduler.framework.gui;

import edu.umn.cs5115.scheduler.framework.IteratorSafeSet;
import edu.umn.cs5115.scheduler.framework.KeyValueAdapter;
import edu.umn.cs5115.scheduler.framework.KeyValueCoding;
import edu.umn.cs5115.scheduler.framework.KeyValueCodingBase;
import edu.umn.cs5115.scheduler.framework.KeyValueObserver;
import edu.umn.cs5115.scheduler.framework.KeyValueObserver.SetMutationType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Transform;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

import edu.umn.cs5115.scheduler.framework.DocumentController;

/**
 * A FakeWidget.  Unlike the real thing, this can support transparency since
 * you're actually drawing directly on the parent component.  FakeWidgets now
 * also support children.
 * @author grant
 */
public class FakeWidget extends KeyValueCodingBase
{
    /** Listen to this to receive notifications of any changes in bounds. */
    public final static String BOUNDS_KEY = "bounds";
    /** Listen to this to receive resize notifications. */
    public final static String SIZE_KEY = "size";
    /** Listen to this to receive movement notifications. */
    public final static String LOCATION_KEY = "location";
    /** Listen to this to receive notifications of any change in location in the
     * canvas.  This is triggered even when the value of location does not 
     * change, but the control's location changes due to a parent's movement. */
    public final static String LOCATION_IN_CANVAS_KEY = "locationInCanvas";
    /** Receive notifications of changes in visibility. */
    public final static String HIDDEN_KEY = "hidden";
    /** Listen to the button's tool tip text. */
    public static String TOOL_TIP_KEY = "toolTip";
    
    /** Just stored for convenience. */
    private final static DocumentController SHARED_DOCUMENT_CONTROLLER = DocumentController.getSharedDocumentController();
    /** FakeWidget we're pretending to be inside. */
    private FakeWidget parent;
    /** List of widgets.  Widgets on top of things are at the bottom of this list. */
    private List<FakeWidget> fakeWidgets = new ArrayList();
    /** Control's bounding rectangle. */
    protected Rectangle bounds = new Rectangle(0, 0, 50, 50);
    /** MouseListeners registered on this object. */
    protected IteratorSafeSet<MouseListener> mouseListeners = new IteratorSafeSet();
    /** MouseMotionListeners registered on this object. */
    protected IteratorSafeSet<MouseMotionListener> mouseMotionListeners = new IteratorSafeSet();
    /**Control to forward mouse events to.  When the mouse goes down, we save 
     * the control that handled it, and continue to forward evetns to it until 
     * the mouse goes up. */
    private FakeWidget savedHandler;
    /** Is the control hidden or not? */
    private boolean hidden;
    /** Tooltip to be displayed on mouse over. */
    private String toolTip = "";

    /** Listens to changes in the parent's location in the canvas, and forwards
     * them to our listeners/children. */
    private KeyValueObserver locationInCanvasListener = new KeyValueAdapter()
    {
        public void valueChanged(KeyValueCoding object, String key)
        {
            didChangeValueForKey(LOCATION_IN_CANVAS_KEY);
        }
    };

    
    /** A type that "implements" both types of mouse listeners.*/
    private static abstract class MouseEventListener implements MouseListener, MouseMotionListener {}
    /** MouseListener that is used by this widget to listen to mouse motion 
     * events in the parent.  This object also forwards the notifications to
     * other listeners.
     */
    private MouseEventListener mouseListener = new MouseEventListener()
    {
        /** Mouse entered - if it's actually inside our bounds, let's trigger an event. */
        public void mouseEntered(MouseEvent e)
        {
            if (!mouseInside && contains(e.x, e.y))
                triggerMouseEntered(e);
        }
        /** Mouse exited - if it was inside before, let's trigger an event. */
        public void mouseExited(MouseEvent e)
        {
            if (mouseInside)
                triggerMouseExited(e);
        }
        
        /** Mouse moved - check for enter/exit, and send mouse moved events. */
        public void mouseMoved(MouseEvent e)
        {
            if (contains(e.x, e.y))
            {
                if (!mouseInside)
                    triggerMouseEntered(e);
                triggerMouseMoved(e);
            } else {
                if (mouseInside)
                    triggerMouseExited(e);
            }
        }
    };
    
    
    /** Listens to ourself for mouse entry/exit events to change the tool tip. */
    private MouseListener mouseEntryListener = new MouseListener()
    {
        public void mouseEntered(MouseEvent e)
        {
            getCanvas().setToolTipText(toolTip);
        }
        public void mouseExited(MouseEvent e)
        {
            // convert to canvas coordinates
            Point mouseAt = convertPoint(new Point(e.x, e.y));
            FakeWidget newOverWidget = getCanvas().getRootWidget().findWidgetAtPoint(mouseAt.x, mouseAt.y);
            if (newOverWidget != null)
            {
                String newToolTip = newOverWidget.getToolTip();
                getCanvas().setToolTipText(newToolTip);
            } else {
                getCanvas().setToolTipText("");
            }
        }
    };
    
    /** Keeps track of whether or not the mouse was inside our bounds, last mouse move event. */
    protected boolean mouseInside = false;
    
    /** Whether or not the mouse listener is currently registered with the parent. */
    protected boolean listening = false;
    
    /** Creates a new instance of FakeWidget */
    public FakeWidget()
    {
        addMouseListener(mouseEntryListener);
    }
    
    /**
     * Adds a child widget to this FakeWidget.
     * @param widget The widget to add.
     */
    public void add(FakeWidget widget)
    {
        fakeWidgets.add(widget);
        widget.setParent(this);
    }
    
    /**
     * Adds a mouse motion listener.  The added listener will receive mouseMoved
     * event whenever the mouse moves over the top of this FakeWidget.
     * @param mouseMotionListener the listener to add.
     */
    public void addMouseMotionListener(MouseMotionListener mouseMotionListener)
    {
        mouseMotionListeners.add(mouseMotionListener);
        checkListening();
    }
    
    /**
     * Remove a mouse m otion listener.
     * @param mouseMotionListener The listener to remove
     * @see #addMouseMotionListener(MouseMotionListener)
     */
    public void removeMouseMotionListener(MouseMotionListener mouseMotionListener)
    {
        mouseMotionListeners.remove(mouseMotionListener);
        checkListening();
    }
    
    /**
     * Add a mouse listener.  The added listener will receive mouse entered and
     * mouse exited events. 
     * @param mouseListener The mouse listener to add.
     */
    public void addMouseListener(MouseListener mouseListener)
    {
        mouseListeners.add(mouseListener);
        checkListening();
    }
    
    /**
     * Remove a mouse listener.
     * @param mouseListener The mouse listener to remove.
     * @see #addMouseListener(MouseListener)
     */
    public void removeMouseListener(MouseListener mouseListener)
    {
        mouseListeners.remove(mouseListener);
        checkListening();
    }
    
    /**
     * Checks to see if we should start/stop listening to our parent for mouse
     * events and does so.
     */
    protected void checkListening()
    {
        if (parent != null)
        {
            if (listening)
            {
                if (mouseListeners.isEmpty() && mouseMotionListeners.isEmpty())
                {
                    listening = false;
                    parent.removeMouseListener(mouseListener);
                    parent.removeMouseMotionListener(mouseListener);
                }
            } else {
                if (!mouseListeners.isEmpty() || !mouseMotionListeners.isEmpty())
                {
                    listening = true;
                    parent.addMouseListener(mouseListener);
                    parent.addMouseMotionListener(mouseListener);
                }
            }
        }
    }
    
    /** Sends mouse moved notifications. */
    protected void triggerMouseMoved(final MouseEvent e)
    {
        int oldX = e.x;
        int oldY = e.y;
        e.x -= bounds.x;
        e.y -= bounds.y;
        mouseMotionListeners.startIterating();
        for(MouseMotionListener listener : mouseMotionListeners)
            listener.mouseMoved(e);
        mouseMotionListeners.finishIterating();
        e.x = oldX;
        e.y = oldY;
    }

    /** Send mouse entered notifications in response to the given event. */
    protected void triggerMouseEntered(MouseEvent e)
    {
        mouseInside = true;

        int oldX = e.x;
        int oldY = e.y;
        e.x -= bounds.x;
        e.y -= bounds.y;
        mouseListeners.startIterating();
        for(MouseListener listener : mouseListeners)
            listener.mouseEntered(e);
        mouseListeners.finishIterating();
        e.x = oldX;
        e.y = oldY;
    }

    /** Send mouse exited notifications in response to the given event. */
    protected void triggerMouseExited(MouseEvent e)
    {
        mouseInside = false;

        int oldX = e.x;
        int oldY = e.y;
        e.x -= bounds.x;
        e.y -= bounds.y;
        mouseListeners.startIterating();
        for(MouseListener listener : mouseListeners)
            listener.mouseExited(e);
        mouseListeners.finishIterating();
        e.x = oldX;
        e.y = oldY;
    }
    
    /** 
     * Called when the fake widget is added to another FakeWidget.  You can 
     * override this method to do setup here, but call super at the beginning of 
     * your implementation.  Note that you do not use this method to add a 
     * FakeWidget to a FakeCanvas, use add(FakeWidget) instead.
     * @param parent The widget to add us to.
     * @see #add(FakeWidget)
     */
    protected void setParent(FakeWidget parent)
    {
        this.parent = parent;
        parent.addListener(locationInCanvasListener, LOCATION_IN_CANVAS_KEY);
        checkListening();
    }
    
    /**
     * Returns the widget's parent FakeWidget.  Note that widgets can be created
     * without being added to a parent.  In that case, this method returns null.
     * @return The parent fake widget, which is the fake widget that contains
     * this widget.
     */
    public FakeWidget getParent()
    {
        return parent;
    }
    
    /**
     * Get the SWT display we're actually running in. 
     * @return The current display in use.
     */
    public Display getDisplay()
    {
        return parent.getDisplay();
    }
    
    /**
     * Get the FakeCanvas we're actually being displayed by.
     * @return The canvas used to display us.
     */
    public FakeCanvas getCanvas()
    {
        if (parent != null)
            return parent.getCanvas();
        else
            return null;
    }
    
    /**
     * Converts a point to the coordinate system of the canvas we're inside.
     */
    public Point convertPoint(Point point)
    {
        Point converted = parent.convertPoint(point);
        converted.x += bounds.x;
        converted.y += bounds.y;
        return converted;
    }
    
    /**
     * Finds the deepest visible widget at a given point in the control.
     * @param x The x-location to find a widget at.  (relative to this control)
     * @param y The y-location to find the widget at.  (relative to this control)
     * @return The deepest descendent of the control at the given point.  If
     * there are no visible children at that point, and the point is within
     * the control's bounds, this method returns the widget itself.  If it is
     * outside the control's bounds, it returns null.
     */
    public FakeWidget findWidgetAtPoint(int x, int y)
    {
        for (FakeWidget child : fakeWidgets)
        {
            if (!child.isHidden() && child.contains(x, y))
                return child.findWidgetAtPoint(x - child.getX(), y - child.getY());
        }
        if (bounds.contains(x, y))
            return this;
        else
            return null;
    }
    
    /**
     * Removes a child widget from the fake widget.
     * @param fakeWidget The widget to remove.
     */
    public void remove(FakeWidget fakeWidget)
    {
        if (fakeWidgets.remove(fakeWidget))
        {
            redraw(fakeWidget.getX(), fakeWidget.getY(), fakeWidget.getWidth(), fakeWidget.getHeight());
            fakeWidget.removeFromParent();
        }
    }
    
    /**
     * Called when the widget is removed using remove(FakeWidget).
     * Override this to do your own clean up, but call super at the end of your
     * implementation.
     * @see #remove(FakeWidget)
     */
    protected void removeFromParent()
    {
        parent.removeListener(locationInCanvasListener, LOCATION_IN_CANVAS_KEY);
    }
    
    /**
     * Sets the tooltip for this fake widget.  The tool tip is a short, one-
     * sentence or phrase description of what the control does that is displayed 
     * when the user hovers the mouse over the control for a certain amount of 
     * time. Normally, only the first letter of a tooltip is capitalized and 
     * there is not a period at the end of the sentence or phrase.
     * @param toolTip The new value of the tool tip.  Set to null or "" to 
     * remove the tool tip for this control.
     * @see #getToolTip()
     */
    public void setToolTip(String toolTip)
    {
        if (toolTip == null)
            toolTip = "";
        
        if (this.toolTip != toolTip)
        {
            this.toolTip = toolTip;
            didChangeValueForKey(TOOL_TIP_KEY);
        }
    }
    
    /**
     * Gets the current tool tip.  The tool tip is a short, one-
     * sentence or phrase description of what the control does that is displayed 
     * when the user hovers the mouse over the control for a certain amount of 
     * time. Normally, only the first letter of a tooltip is capitalized and 
     * there is not a period at the end of the sentence or phrase.
     * @return The last set toolTip.  If there is no tool tip, this method 
     * returns an empty string ("").
     * @see #setToolTip(String)
     */
    public String getToolTip()
    {
        return toolTip;
    }
    
    /**
     * Moves the specified control to the front. This causes the area covered
     * by the widget to be redrawn.
     * @param fakeWidget The widget to move to the front.  If this widget isn't
     * already in the canvas, this method does nothing.
     */
    public void bringToFront(FakeWidget fakeWidget)
    {
        if (fakeWidgets.remove(fakeWidget))
        {
            fakeWidgets.add(fakeWidget);
            fakeWidget.redraw();
        }
    }

    /**
     * Sends the specified control to the back.  This causes the area covered
     * by the widget to be redrawn.
     * @param fakeWidget The widget to send to the back.  If the widget isn't in
     * the canvas, this method does nothing.
     */
    void sendToBack(FakeWidget fakeWidget)
    {
        if (fakeWidgets.remove(fakeWidget))
        {
            fakeWidgets.add(0, fakeWidget);
            fakeWidget.redraw();
        }
    }
    
    /** 
     * Get the width of this control.
     * @return The width of the control, in pixels.
     */
    public int getWidth()
    {
        return bounds.width;
    }
    
    /**
     * Get the height of this control.
     * @return The height of the control, in pixels.
     */
    public int getHeight()
    {
        return bounds.height;
    }
    
    /**
     * Get the location of the left edge of the control.
     * @return The left edge of the control, in the parent's coordinate system.
     */
    public int getX()
    {
        return bounds.x;
    }
    
    /**
     * Get the location of the top edge of the control.
     * @return The top edge of the control, in the parent's coordinate system.
     */
    public int getY()
    {
        return bounds.y;
    }
    
    /**
     * Shows or hides the widget.  Hidden controls do not receive paint or mouse
     * click events.
     * @param b True to hide the control, false to show it again.
     * @see #isHidden()
     */
    public void setHidden(boolean b)
    {
        if (hidden != b)
        {
            hidden = b;
            redraw();
            didChangeValueForKey(HIDDEN_KEY);
        }
    }
    
    /**
     * Check if the control is hidden or shown.
     * @return True if the control is hidden, false if it is visible.
     * @see #setHidden(boolean)
     */
    public boolean isHidden()
    {
        return hidden;
    }
    
    /**
     * Check if the control, or any of its parents, are hidden.
     * @return True if the control (or one of its parents) is hidden, and thus
     * the control will not be drawn.  Returns false if the control is visible
     * and being drawn. (this does not necessarily mean it is positioned such
     * that it is visible, merely that it is not being hidden)
     */
    public boolean isAnyParentHidden()
    {
        if (parent == null)
            return hidden;
        else
            return hidden || parent.isAnyParentHidden();
    }
    
    /**
     * Change the bounding rectangle of this control.  The parent will be 
     * notified that the region that was previously occupied by this control
     * and region now occupied by the control will need to be redrawn.
     * @param x The new left edge of the control, in the parent's coordinate system.
     * @param y The new top edge of the control, in the parent's coordinate system.
     * @param width The new width of the control, in pixels.
     * @param height The new height of the control, in pixels.
     * @see #setLocation(int, int)
     */
    public void setBounds(int x, int y, int width, int height)
    {
        boolean moved;
        boolean resized;
        if (bounds.x != x || bounds.y != y)
            moved = true;
        else
            moved = false;
        if (bounds.width != width || bounds.height != height)
            resized = true;
        else
            resized = false;
        if (moved || resized)
        {
            int oldX = bounds.x;
            int oldY = bounds.y;
            int oldWidth = bounds.width;
            int oldHeight = bounds.height;
            redraw(); // redraw the control's current area
            bounds.x = x;
            bounds.y = y;
            bounds.width = width;
            bounds.height = height;
            redraw(); // redraw the control's new area.
            didChangeValueForKey(BOUNDS_KEY);
            if (moved)
            {
                didChangeValueForKey(LOCATION_KEY);
                didChangeValueForKey(LOCATION_IN_CANVAS_KEY);
            }
            if (resized)
                didChangeValueForKey(SIZE_KEY);
        }
    }
    
    /**
     * Set the widget's bounds.
     * @param r The rectangle containing the new bounds.
     * @see #setBounds(int, int, int, int)
     */
    public void setBounds(Rectangle r)
    {
        setBounds(r.x, r.y, r.width, r.height);
    }
    
    /**
     * Get the widget's bounds
     * @return A copy of the bounding rectangle.
     */
    public Rectangle getBounds()
    {
        return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
    }
    
    /**
     * Move the widget.
     * @param x The new x location for the top-left corner of the control, 
     * in the parent's cooridnate system.
     * @param y The new y location for the top-left corner of the control, 
     * in the parent's cooridnate system.
     */
    public void setLocation(int x, int y)
    {
        setBounds(x, y, bounds.width, bounds.height);
    }
    
    /**
     * Move the widget.
     * @param p A point containing the new position for the upper-left corner
     * of the widget.
     * @see #setLocation(int, int)
     */
    public void setLocation(Point p)
    {
        setBounds(p.x, p.y, bounds.width, bounds.height);
    }
    
    /**
     * Get the location of the widget.
     * @return A point containing the location of the upper-left corner of the
     * widget, in it's parent's coordinate system.
     * @see #getX()
     * @see #getY()
     */
    public Point getLocation()
    {
        return new Point(bounds.x, bounds.y);
    }
    
    /**
     * Get the location of the canvas in the widget.
     * @return The location of the canvas, relative to the parent canvas.
     */
    public Point getLocationInCanvas()
    {
        return convertPoint(new Point(0, 0));
    }
    
    /**
     * Change the size of the widget.
     * @param width The new width for the widget, in pixels.
     * @param height The new height for the widget, in pixels.
     */
    public void setSize(int width, int height)
    {
        setBounds(bounds.x, bounds.y, width, height);
    }
    
    /**
     * Resize the widget.
     * @param p A point whose x coordinate contains the new width, and whose y
     * coordinate contains the new height.
     * @see #setSize(int, int)
     */
    public void setSize(Point p)
    {
        setBounds(bounds.x, bounds.y, p.x, p.y);
    }
    
    /**
     * Get the size of the widget.
     * @return A point whose x-coordinate contains the width and whose y 
     * coordinate contains the height.
     * @see #getWidth()
     * @see #getHeight()
     */
    public Point getSize()
    {
        return new Point(bounds.x, bounds.y);
    }
    
    /** Causes the entire bounds of the control to be redrawn. */
    public void redraw()
    {
        if (parent != null)
            parent.redraw(bounds.x, bounds.y, bounds.width, bounds.height);
    }
    /** Causes the specified rectangle to be redrawn
     * @param x The x-coordinate, relative to this control, that the rectangle
     * needing to be redrawn begins at.
     * @param y The y-coordinate.
     * @param width The width of the rectangle to redraw
     * @param height The height of the rectangle to redraw.
     */
    public void redraw(int x, int y, int width, int height)
    {
        if (parent != null)
            parent.redraw(bounds.x+x, bounds.y+y, width, height);
    }
    
    /**
     * Handles a paint event from the parent if it is inside our bounds.  Don't
     * override this method, use paintComponent instead.
     * @param e The PaintEvent to check.
     * @see #paintComponent(PaintEvent)
     */
    void handlePaintEvent(PaintEvent e)
    {
        if (!hidden && bounds.intersects(e.x, e.y, e.width, e.height))
        {
            GC g = e.gc;
            Rectangle oldClip = g.getClipping();
            Rectangle newClip = bounds.intersection(oldClip);
            //g.setClipping(newClip);
            // translate the gc to our origin.
            Transform transform = new Transform(SHARED_DOCUMENT_CONTROLLER.getDisplay());
            g.getTransform(transform);
            float[] transformationElements = new float[6];
            transform.getElements(transformationElements);
            // translate back to identity transform to set the clipping - it's buggy otherwise
            g.setTransform(null);
            g.setClipping((int)(newClip.x + transformationElements[4]), (int)(newClip.y+transformationElements[5]), newClip.width, newClip.height);            
            // add some to the transformation and restore it
            transform.translate(bounds.x, bounds.y);
            g.setTransform(transform);
            //Rectangle newClip = oldClip.intersection(bounds);
            //newClip = g.getClipping();
            //g.setClipping(g.getClipping());
            
            Rectangle oldLocation = new Rectangle(e.x, e.y, e.width, e.height);
            Rectangle newLocation = bounds.intersection(oldLocation);
            
            e.x = newLocation.x - bounds.x;
            e.y = newLocation.y - bounds.y;
            e.width = newLocation.width;
            e.height = newLocation.height;
            
            paintComponent(g);
            
            // paint all child Fake Widgets.
            for(FakeWidget fakeWidget : fakeWidgets)
                fakeWidget.handlePaintEvent(e);
            
            e.x = oldLocation.x;
            e.y = oldLocation.y;
            e.width = oldLocation.width;
            e.height = oldLocation.height;
            
            transform.translate(-bounds.x, -bounds.y);
            g.setTransform(transform);
            g.setClipping(oldClip);
            transform.dispose();
        }
    }
    /**
     * Handles a momuse event from the parent if it is inside our bounds.  Don't
     * override this method, use one of the other mouse event handlers instead.
     * @param e The PaintEvent to check.
     * @see #mouseDoubleClick(MouseEvent)
     * @see #mouseDown(MouseEvent)
     * @see #mouseUp(MouseEvent)
     */
    boolean handleMouseEvent(MouseEvent e, FakeCanvas.Dispatcher dispatcher)
    {
        boolean handled = false;
        
        int oldX = e.x;
        int oldY = e.y;
        e.x -= bounds.x;
        e.y -= bounds.y;
        
        if (savedHandler == null)
        {
            boolean saveHandler = dispatcher.getShouldSaveHandler();
            Iterator<FakeWidget> iterator = getReverseWidgetIterator(fakeWidgets);
            while(iterator.hasNext())
            {
                FakeWidget childWidget = iterator.next();
                if (!childWidget.isHidden() && childWidget.contains(e.x, e.y) && childWidget.handleMouseEvent(e, dispatcher))
                {
                    if (saveHandler)
                        savedHandler = childWidget;
                    handled = true;
                    break;
                }
            }
            if (!handled && dispatcher.dispatchEvent(e, this))
                handled = true;
        } else {
            savedHandler.handleMouseEvent(e, dispatcher);
            if (dispatcher.getShouldRevertToNormal())
                savedHandler = null;
        }
        e.x = oldX;
        e.y = oldY;
        
        return handled;
    }
        
    /**
     * Gets an iterator over the fake widgets that goes backwards.
     * @return An iterator which iterates over the list of fake widgets
     * backwards.
     */
    private Iterator<FakeWidget> getReverseWidgetIterator(final List<FakeWidget> fakeWidgets)
    {
        return new Iterator()
        {
            private int currentIndex = fakeWidgets.size();
            public boolean hasNext()
            {
                return (currentIndex > 0);
            }
            public Object next()
            {
                currentIndex--;
                return fakeWidgets.get(currentIndex);
            }
            public void remove()
            {
                throw new UnsupportedOperationException("Remove not supported.");
            }
        };
    }
    
    /** 
     * Paint your custom fake control.  Override this method.
     * @param g A graphics context, ready to use.  It has been translated to
     * your bounds and the clipping region has been set so you can't draw 
     * outside your control.
     */
    public void paintComponent(GC g) {}
    
    /**
     * Test whether or not a point is inside the receiver.
     * @param x The x-coordinate, in the parent's coordinate system.
     * @param y The y-coordinate, in the parent's coordinate system.
     * @return True if the point is inside the receiver, false otherwise.
     */
    public boolean contains(int x, int y)
    {
        return bounds.contains(x, y);
    }
    
    /**
     * Handle a mouse double-click event, if you like.
     * @param e The mouse down event.  The coordinates are in terms of your 
     * control.
     * @return True to handle this event, false to ignore it and allow the
     * event to be passed down to lower controls (allow your control to be
     * clicked through).  Default implementation returns true.
     */
    public boolean mouseDoubleClick(MouseEvent e) { return true; }
    /**
     * Handle a mouse down event, if you like.
     * @param e The mouse down event.  The coordinates are in terms of your 
     * control.
     * @return True to handle this event, false to ignore it and allow the
     * event to be passed down to lower controls (allow your control to be
     * clicked through).  Default implementation returns true.
     */
    public boolean mouseDown(MouseEvent e) { return true; }
    /**
     * Handle a mouse up event, if you like.
     * @param e The mouse down event.  The coordinates are in terms of your 
     * control.
     * @return True to handle this event, false to ignore it and allow the
     * event to be passed down to lower controls (allow your control to be
     * clicked through).  Default implementation returns true.
     */
    public boolean mouseUp(MouseEvent e) { return true; }
}
